package com.tentime.saas.tenant.config;

import java.util.HashMap;
import java.util.Map;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.tentime.saas.tenant.system.model.User;
import com.tentime.saas.tenant.system.repository.UserRepository;
import com.tentime.saas.tenant.system.service.UserService;

/**
 * 动态配置多租户数据源.
 * 
 * @author chlingm
 *
 */
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = { "com.tentime.saas.tenant.system.repository",
        "com.tentime.saas.tenant.system.model" })
@EnableJpaRepositories(basePackages = {
        "com.tentime.saas.tenant.system.repository",
//        "com.tentime.saas.tenant.system.service.impl",
        "com.tentime.saas.tenant.system.service" }, 
        entityManagerFactoryRef = "tenantEntityManagerFactory", 
        transactionManagerRef = "tenantTransactionManager")
public class TenantDatabaseConfig
//implements TransactionManagementConfigurer
	{

    private static final Logger LOG = LoggerFactory
            .getLogger(TenantDatabaseConfig.class);

    @Bean(name = "tenantJpaVendorAdapter")
    public JpaVendorAdapter jpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }
    
    /**
     * 事务无法生效，repository.save无法生效.
     */
    /*@Bean(name = "tenantTransactionManager")
    public PlatformTransactionManager  transactionManager(@Qualifier("tenantEntityManagerFactory") 
            EntityManagerFactory tenantEntityManager) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(tenantEntityManager);
        return transactionManager;
    }*/
    /**
     * 使用factory重新new出来一个 JpaTransactionManager
     * @param tenantEntityManagerFactory
     * @return
     */
    @Bean(name = "tenantTransactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("tenantEntityManagerFactory") 
    LocalContainerEntityManagerFactoryBean tenantEntityManagerFactory) {
    	return new JpaTransactionManager(tenantEntityManagerFactory.getObject());
    }
    
    /**
     * 解决实体懒加载，no session问题
     * application.yml配置spring.jpa.open-in-view=true
     * @return
     */
    @Bean
	public OpenEntityManagerInViewFilter openEntityManagerInViewFilter(){
    	OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
    	filter.setEntityManagerFactoryBeanName("tenantEntityManagerFactory");
		return filter;
	}
    
    @Bean(name = "datasourceBasedMultitenantConnectionProvider")
    @ConditionalOnBean(name = "masterEntityManagerFactory")
    public MultiTenantConnectionProvider multiTenantConnectionProvider() {
        // Autowires the multi connection provider
        return new DataSourceBasedMultiTenantConnectionProviderImpl();
    }

    @Bean(name = "currentTenantIdentifierResolver")
    public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
        return new CurrentTenantIdentifierResolverImpl();
    }

    
    @Bean(name = "tenantEntityManagerFactory")
    @ConditionalOnBean(name = "datasourceBasedMultitenantConnectionProvider")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            @Qualifier("datasourceBasedMultitenantConnectionProvider") 
            MultiTenantConnectionProvider connectionProvider,
            @Qualifier("currentTenantIdentifierResolver") 
            CurrentTenantIdentifierResolver tenantResolver) {

        LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
        //All tenant related entities, repositories and service classes must be scanned
        emfBean.setPackagesToScan(
                new String[] { User.class.getPackage().getName(),
                        UserRepository.class.getPackage().getName(),
                        UserService.class.getPackage().getName() });
        emfBean.setJpaVendorAdapter(jpaVendorAdapter());
        emfBean.setPersistenceUnitName("tenantdb-persistence-unit");
        Map<String, Object> properties = new HashMap<>();
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT,
                MultiTenancyStrategy.SCHEMA);
        properties.put(
                org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER,
                connectionProvider);
        properties.put(
                org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER,
                tenantResolver);
        // ImprovedNamingStrategy is deprecated and unsupported in Hibernate 5
        // properties.put("hibernate.ejb.naming_strategy",
        // "org.hibernate.cfg.ImprovedNamingStrategy");
        properties.put(org.hibernate.cfg.Environment.DIALECT,
                "com.tentime.saas.common.dialect.MySQLDialectUTF8");
        properties.put(org.hibernate.cfg.Environment.SHOW_SQL, true);
        properties.put(org.hibernate.cfg.Environment.FORMAT_SQL, true);
        properties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, "update");

        emfBean.setJpaPropertyMap(properties);
        LOG.info("tenantEntityManagerFactory set up successfully!");
        return emfBean;
    }
    
}